package com.slipper.core.similarity;

import com.slipper.exception.CommonException;
import com.slipper.util.Tool;
import org.springframework.beans.BeanWrapperImpl;

import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SimilarityDealer<T> {
    private final List<T> dataList;
    private final String idProp;
    private final String textProp;
    private final double similarity;
    private List<T> nullList;
    private Map<Object, T> idObjectMap;
    private Map<Object, Map<String, Long>> idWordFrequencyMap;
    private List<List<T>> groupDataList;

    public SimilarityDealer(List<T> dataList, String idProp, String textProp, double similarity) {
        this.dataList = dataList;
        this.idProp = idProp;
        this.textProp = textProp;
        this.similarity = similarity;

        if (this.dataList == null || this.dataList.isEmpty()) {
            throw new CommonException("数据列表不能为空！");
        }
        if (Tool.isBlank(this.idProp)) {
            throw new CommonException("主键字段名不能为空！");
        }
        if (Tool.isBlank(this.textProp)) {
            throw new CommonException("相似度文本字段名不能为空！");
        }
        if (similarity <= 0) {
            throw new CommonException("相似度必须大于0！");
        }

        this.splitWord();
        this.similarityGroup();
    }

    private void splitWord() {
        this.nullList = new ArrayList<>();
        this.idObjectMap = new HashMap<>();
        this.idWordFrequencyMap = new HashMap<>();
        this.dataList.forEach(data -> {
            BeanWrapperImpl beanWrapper = new BeanWrapperImpl(data);
            Object id = beanWrapper.getPropertyValue(this.idProp);
            Object text = beanWrapper.getPropertyValue(this.textProp);
            if (Tool.isNotBlank(text)) {
                this.idObjectMap.put(id, data);
                this.idWordFrequencyMap.put(id, HanLPUtil.splitWordAndFrequency(text.toString()));
            } else {
                this.nullList.add(data);
            }
        });
    }

    private void similarityGroup() {
        try (ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())) {
            Map<Object, Set<Object>> idGroupMap = new HashMap<>();
            this.idWordFrequencyMap.forEach((key, value) -> {
                Set<Object> idSet = new HashSet<>();
                idGroupMap.put(key, idSet);
                service.submit(new Task(idSet, value, this.idWordFrequencyMap, this.similarity));
            });
            service.shutdown();
            boolean result = service.awaitTermination(Integer.MAX_VALUE, TimeUnit.MINUTES);
            if (!result) {
                throw new CommonException("相似度计算超时！");
            }

            Map<Object, Set<Object>> idGroupLink = new HashMap<>();
            List<Set<Object>> groupList = new ArrayList<>();
            idGroupMap.keySet().forEach(key -> this.concatGroup(idGroupMap, key, groupList, idGroupLink));

            this.groupDataList = new ArrayList<>();
            groupList.forEach(set -> {
                List<T> itemList = new ArrayList<>();
                set.forEach(id -> itemList.add(idObjectMap.get(id)));
                if (!itemList.isEmpty()) {
                    this.groupDataList.add(itemList);
                }
            });
            if (!this.nullList.isEmpty()) {
                this.groupDataList.add(this.nullList);
            }
        } catch (Exception e) {
            throw new CommonException("相似度计算出错！");
        }
    }

    private void concatGroup(
            Map<Object, Set<Object>> idGroupMap,
            Object id,
            List<Set<Object>> groupList,
            Map<Object, Set<Object>> idGroupLink) {
        Set<Object> groupIdSet = new HashSet<>();
        concatGroupItem(idGroupMap, id, groupIdSet, idGroupLink);
        if (!groupIdSet.isEmpty()) {
            groupList.add(groupIdSet);
        }
    }

    private void concatGroupItem(
            Map<Object, Set<Object>> idGroupMap,
            Object id,
            Set<Object> groupIdSet,
            Map<Object, Set<Object>> idGroupLink) {
        if (!idGroupMap.containsKey(id) || idGroupLink.containsKey(id)) return;
        groupIdSet.add(id);
        idGroupLink.put(id, groupIdSet);

        for (Object itemId : idGroupMap.get(id)) {
            concatGroupItem(idGroupMap, itemId, groupIdSet, idGroupLink);
        }
    }

    public List<List<T>> getSimilarityGroup() {
        return this.groupDataList;
    }
}

class Task implements Runnable {
    private final Set<Object> idSet;
    private final Map<String, Long> wordFrequencyMap;
    private final Map<Object, Map<String, Long>> idWordFrequencyMap;
    private final double similarity;

    public Task(
            Set<Object> idSet,
            Map<String, Long> wordFrequencyMap,
            Map<Object, Map<String, Long>> idWordFrequencyMap,
            double similarity) {
        this.idSet = idSet;
        this.wordFrequencyMap = wordFrequencyMap;
        this.idWordFrequencyMap = idWordFrequencyMap;
        this.similarity = similarity;
    }

    public void run() {
        this.idWordFrequencyMap.forEach((key, value) -> {
            if (CosineSimilarity.similarity(this.wordFrequencyMap, value) >= this.similarity) {
                this.idSet.add(key);
            }
        });
    }
}